#include "proxy/curl/curlconn.h"
#include "curl/curl.h"
#include "json/json.h"
#include <Shlwapi.h>
#include "proxy/md5/md5.h"
#include "proxy/conn/flowinstance.h"

namespace proxy{

  struct FtpDownloadStruct{
    FILE *fp;
    md5_state_t state;
  };


  static const std::string REGISTER_ERROR = "Register error";
  static const std::string PING_ERROR = "Ping Error";
  static const std::string REPLEASE_FLAG = "RESET";
  static const std::string RETURN_ERROR = "ERROR";
  static const std::string RETURN_SUCCEED = "SUCCEES";

  static size_t write_callback(void *data, size_t size, size_t nmemb, void *user_data) {
    int size_data = size * nmemb;
    ((std::string *)(user_data))->append((const char *)data, size_data);
    return size_data;
  }

  static size_t file_write_callback(void *data, size_t size, size_t nmemb, void *user_data){
    DLOG(INFO) << size * nmemb;
    size_t data_size = size * nmemb;
    FtpDownloadStruct *pfdls = (FtpDownloadStruct *)user_data;
    md5_append(&pfdls->state, (const md5_byte_t *)data, data_size);
    return fwrite(data, size, nmemb, pfdls->fp);
  }

  CurlClientSession::CurlClientSession(boost::asio::io_service &io_service,
    SessionType session_type, int timeout, std::string &verion_key)
    :io_service_(io_service),
    session_type_(session_type),
    timeout_(timeout),
    flow_(1),
    is_connect_(false),
    is_update_checking_(false),
    verion_key_(verion_key){

    curl_global_init(CURL_GLOBAL_ALL);
#ifdef WIN32
    DWORD dwRetVal = 0;
    TCHAR path_buffer[MAX_PATH];
    // Check the process return
    dwRetVal = GetTempPathA(MAX_PATH, path_buffer);
    save_path_ = path_buffer;
#else
    save_path_ = "";
#endif
  }

  CurlClientSession::~CurlClientSession(){
    curl_global_cleanup();
  }

  void CurlClientSession::StartServerSession(std::string host, std::string url){
    BOOST_ASSERT(session_type_ == SESSION_TYPE_SERVER);
    boost::thread(boost::bind(
      &CurlClientSession::HandleThreadServerSession, shared_from_this(), host, url));
  }

  void CurlClientSession::HandleThreadServerSession(
    std::string host, std::string url){
    LOG(WARNING) << "START SERVER SESSON RUNNING ... ...";
    BOOST_ASSERT(session_type_ == SESSION_TYPE_SERVER);
    std::string ping_url = ProcessUrl(host + "/" + url);
    if (ping_url.empty() || ping_url == RETURN_ERROR){
      io_service_.post(boost::bind(boost::ref(SignalErrorMessage),
        shared_from_this(), REGISTER_ERROR_CODE));
      return;
    }
    else{
      io_service_.post(boost::bind(boost::ref(SignalLoginSucceed), shared_from_this()));
    }
    is_connect_ = true;
    LoopPing(host + ping_url);
    LOG(WARNING) << "STOP SERVER SESSION THREAD ... ...";
  }

  //
  void CurlClientSession::StartClientSession(std::string host, std::string url){
    BOOST_ASSERT(session_type_ == SESSION_TYPE_CLIENT);
    boost::thread(boost::bind(
      &CurlClientSession::HandleThreadClientSession, shared_from_this(), host, url));
  }

  void CurlClientSession::HandleThreadClientSession(std::string host, std::string url){
    LOG(WARNING) << "START CLIENT SESSON RUNNING ... ...";
    BOOST_ASSERT(session_type_ == SESSION_TYPE_CLIENT);
    std::string ping_url = ProcessUrl(host + "/" + url);
    if (ping_url.empty() || ping_url == RETURN_ERROR){
      io_service_.post(boost::bind(boost::ref(SignalErrorMessage),
        shared_from_this(), REGISTER_ERROR_CODE));
      return;
    }
    else{
      io_service_.post(boost::bind(boost::ref(SignalLoginSucceed), shared_from_this()));
    }
    is_connect_ = true;
    LoopPing(host + ping_url);
    LOG(WARNING) << "STOP CLIENT SESSION THREAD ... ...";
  }

  void CurlClientSession::StartGetOnlineServerInfor(std::string host, std::string url){
    BOOST_ASSERT(session_type_ == SESSION_TYPE_CLIENT);
    boost::thread(boost::bind(
      &CurlClientSession::HandleThreadGetServerInfor,
      shared_from_this(), host, url));
  }

  bool CurlClientSession::StartFtpDownload(std::string ftp_url,
    std::string authen, std::string save_path){
    CURL *curl;
    CURLcode res;
    FILE *fp = NULL;
    fp = fopen(save_path.c_str(), "w");
    if (fp == NULL){
      return false;
    }

    curl = curl_easy_init();
    if (curl) {
      curl_easy_setopt(curl, CURLOPT_URL, ftp_url.c_str());
      curl_easy_setopt(curl, CURLOPT_USERPWD, authen.c_str());
      curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, file_write_callback);
      curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
      res = curl_easy_perform(curl);
      /* Check for errors */
      if (res != CURLE_OK){
        LOG(ERROR) << "curl_easy_perform() failed: %s" << curl_easy_strerror(res);
      }
      curl_easy_cleanup(curl);
    }
    fclose(fp);
    return true;
  }

  bool CurlClientSession::StartCheckUpdateAndDownload(std::string check_url,
    std::string authen, std::string save_path, std::string version_name){
    BOOST_ASSERT(session_type_ == SESSION_TYPE_CLIENT);
    if (is_update_checking_){
      return false;
    }
    is_update_checking_ = true;
    boost::thread(boost::bind(
      &CurlClientSession::HandleThreadCheckUpdateAndDownload,
      shared_from_this(), check_url, authen, save_path, version_name));
    return true;
  }

  void CurlClientSession::HandleThreadGetServerInfor(std::string host, std::string url){
    std::string result = ProcessUrl(host + "/" + url);
    io_service_.post(boost::bind(boost::ref(SignalServerInfor), shared_from_this(), result));
  }

  std::string CurlClientSession::ProcessUrl(std::string url){
    CURL *curl;
    CURLcode res;
    std::string result;

    curl = curl_easy_init();
    if (curl) {
      curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
      curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
      curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result);
      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
      /* Perform the request, res will get the return code */
      res = curl_easy_perform(curl);
      /* Check for errors */
      if (res != CURLE_OK){
        LOG(ERROR) << "curl_easy_perform() failed: %s" << curl_easy_strerror(res);
        result.clear();
      }
      curl_easy_cleanup(curl);
    }
    return result;
  }

  void CurlClientSession::LoopPing(std::string url){

    CURL *curl;
    CURLcode res;
    std::string result;
    curl = curl_easy_init();
    int reconnect_timeout = 0;
    while (curl && is_connect_){
      result.clear();
      std::string flow_url = url;
      flow_url.replace(flow_url.find(REPLEASE_FLAG),
        REPLEASE_FLAG.size(), IntegerToString(FlowInstance::ResetDownFlow()));
      LOG(INFO) << flow_url;
      curl_easy_setopt(curl, CURLOPT_URL, flow_url.c_str());
      curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
      curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result);
      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);

      /* Perform the request, res will get the return code */
      res = curl_easy_perform(curl);
      /* Check for errors */
      if (res != CURLE_OK){
        LOG(ERROR) << "curl_easy_perform() failed: %s" << curl_easy_strerror(res);
        reconnect_timeout++;
        if (reconnect_timeout > 4){
          // Failure to connect the server
          io_service_.post(boost::bind(boost::ref(SignalErrorMessage),
            shared_from_this(), DISCONNECT_SERVER_TIMEOUT));
          break;
        }
        //break;
      }
      else if (session_type_ == SESSION_TYPE_SERVER){
        // Do nothing
      }
      else {
        reconnect_timeout = 0;
        //io_service_.post(boost::bind(boost::ref(SignalMessage),
        //  shared_from_this(), result));
        int res = VerifyPingMessage(result);
        if (res != MESSAGE_OK_SUCCEED){
          io_service_.post(boost::bind(boost::ref(SignalErrorMessage),
            shared_from_this(), res));
          break;
        }
      }
#ifdef WIN32
      Sleep(timeout_ * 1000);
#else
      sleep(timeout_);
#endif
      //flow_ = rand() % 65536;
    }
    if (curl){
      curl_easy_cleanup(curl);
    }
  }

  int CurlClientSession::VerifyPingMessage(std::string &msg){
    Json::Value root;
    Json::Reader reader;
    if (!reader.parse(msg, root)){
      return PARSE_PING_JSON_ERROR;
    }
    int code = root[PING_ERROR_CODE_STR].asInt();
    if (code == PING_ERROR_CODE_TIME_EXCEED){
      return CLIENT_REMAIN_TIME_EXCEED;
    }
    if (code != PING_ERROR_CODE_OK || verion_key_.size() != CONST_STRING_KEY_SIZE){
      return CLIENT_VERSION_KERY_ERROR;
    }
    std::string file_key = root[WINDOW_FTP_DOWNLOAD_KEY_NAME].asString();
    std::string update_key = root[WINDOW_CLIENT_VERSION_KEY].asString();
    std::string ftp_url = root[WINDOW_CLIENT_FTP_URL].asString();
    std::string authen = root[WINDOW_CLIENT_C_FTP_AUTHEN].asString();
    int remain_time = root[CLIENT_REMAIN_TIME].asInt();

    // CheckTheVerion
    if (remain_time <= 0){
      return CLIENT_REMAIN_TIME_EXCEED;
    }

    // the current verion is the newest verion
    if (verion_key_ == update_key && verion_key_.size() == CONST_STRING_KEY_SIZE){
      return MESSAGE_OK_SUCCEED;
    }

    // New Update
    // Check File exsit
    std::string path_name = save_path_ + update_key + ".exe";
    bool file_exists = false;
#ifdef WIN32
    if (PathFileExists(path_name.c_str()) == TRUE){
      std::string file_key;
      if (GeneratorFileKey(path_name.c_str(), file_key)){
        file_exists = true;
        DLOG(INFO) << "[FILE] " << path_name << "EXISTS!";
      }
    }
    else{
      file_exists = false;
      DLOG(INFO) << "[FILE] " << path_name << "NOT EXISTS!";
    }
#else
#endif
    if (!file_exists){
      DLOG(INFO) << "Start Download the file";
      if (!FtpDownload(ftp_url, authen, path_name, file_key)){
        DLOG(INFO) << "DOWNLOAD FILE ERROR ... ...";
        return MESSAGE_OK_SUCCEED;
      }
      DLOG(INFO) << "New Version .... ....";
    }
    SignalUpdateMessage(shared_from_this(), path_name);
    return MESSAGE_OK_SUCCEED;
  }

  void CurlClientSession::HandleThreadCheckUpdateAndDownload(std::string check_url,
    std::string authen, std::string save_path, std::string version_name){
    while (1){
      std::string result = ProcessUrl(check_url);
      if (result.empty()){
        break;
      }
      Json::Value root;
      Json::Reader reader;
      if (!reader.parse(result, root)){
        break;
      }
      int ver_num = root[JSON_CHECK_UPDATE_VERSION_CODE].asInt();
      std::string ver_key = root[JSON_CHECK_UPDATE_VERSION_KEY].asString();
      std::string ftp_url = root[JSON_CHECK_UPDATE_FTP_URL].asString();
      if (ver_key == version_name && ver_key.size() == CONST_STRING_KEY_SIZE){
#ifdef WIN32
        Sleep(timeout_ * 10000);
#else
        sleep(timeout_);
#endif
        continue;
      }
      // New Update
      // Check File exsit
      std::string path_name = save_path + ver_key + ".exe";
      bool file_exists = false;
#ifdef WIN32
      if (PathFileExists(path_name.c_str()) == TRUE){
        std::string file_key;
        if (GeneratorFileKey(path_name.c_str(), ver_key)){
          file_exists = true;
        }
        DLOG(INFO) << "[FILE] " << path_name << "EXISTS!";
      }
      else{
        file_exists = false;
        DLOG(INFO) << "[FILE] " << path_name << "NOT EXISTS!";
      }
#else
#endif
      if (!file_exists){
        DLOG(INFO) << "Start Download the file";
        if (!FtpDownload(ftp_url, authen, path_name, ver_key)){
          DLOG(INFO) << "DOWNLOAD FILE ERROR ... ...";
#ifdef WIN32
          Sleep(timeout_ * 1000);
#else
          sleep(timeout_);
#endif
          continue;
        }
        DLOG(INFO) << "New Version .... ....";
      }
      SignalUpdateMessage(shared_from_this(), path_name);
#ifdef WIN32
      Sleep(timeout_ * 1000);
#else
      sleep(timeout_);
#endif
      // Notify appliction layout
    }
    is_update_checking_ = false;
  }


  bool CurlClientSession::FtpDownload(std::string &ftp_url, std::string &authen,
    std::string &path_name, std::string &ver_key){
    CURL *curl;
    CURLcode res;
    static const int ftp_response_timeout = 30 * 60;
    FtpDownloadStruct fdls;
    md5_byte_t digest[CONST_KEY_SIZE];

    md5_init(&fdls.state);
    fdls.fp = NULL;
    fdls.fp = fopen(path_name.c_str(), "wb");
    if (fdls.fp == NULL){
      return false;
    }
    curl = curl_easy_init();
    if (curl) {
      curl_easy_setopt(curl, CURLOPT_URL, ftp_url.c_str());
      curl_easy_setopt(curl, CURLOPT_USERPWD, authen.c_str());
      curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, file_write_callback);
      curl_easy_setopt(curl, CURLOPT_WRITEDATA, &fdls);
      res = curl_easy_perform(curl);
      /* Check for errors */
      if (res != CURLE_OK && res != CURLE_OPERATION_TIMEDOUT){
        LOG(ERROR) << "curl_easy_perform() failed: %s" << curl_easy_strerror(res);
      }
      curl_easy_cleanup(curl);
    }
    fclose(fdls.fp);

    md5_finish(&fdls.state, digest);
    return Md5Compare(digest, ver_key);
  }

  std::string CurlClientSession::IntegerToString(int n){
    if (n <= 1){
      return "1";
    }
    std::string result;
    while (n > 0){
      char t = n % 10 + '0';
      result.push_back(t);
      n /= 10;
    }
    std::reverse(result.begin(), result.end());
    return result;
  }

  bool CurlClientSession::GeneratorFileKey(
    const char *path_name, std::string &ver_key){
    FILE * fp = fopen(path_name, "rb");
    if (fp == NULL){
      return false;
    }
    md5_state_t state;
    md5_byte_t digest[16];
    md5_init(&state);
    unsigned char temp_buffer[512];
    while (!feof(fp)){
      int size = fread(temp_buffer, 1, 512, fp);
      md5_append(&state, (const md5_byte_t *)temp_buffer, size);
    }
    fclose(fp);
    md5_finish(&state, digest);
    return Md5Compare(digest, ver_key);
  }

  bool CurlClientSession::Md5Compare(unsigned char *digest, std::string &key){
    std::string digest_key;
    char temp_buffer[CONST_KEY_SIZE];
    for (int i = 0; i < CONST_KEY_SIZE; i++){
      sprintf(temp_buffer, "%02x", digest[i]);
      digest_key.append(temp_buffer, 2);
    }
    if (digest_key == key){
      return true;
    }
    return false;
  }

}